﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;

using Buddy.Common;
using Buddy.Common.Plugins;
using Buddy.Common.Xml;
using Buddy.CommonBot;
using Buddy.Swtor;
using Buddy.Swtor.Objects;
using Buddy.XmlEngine;

namespace Buddywing.Plugins.AutoEquip
{
    public class Equipper : IPlugin
    {
        #region Implementation of IEquatable<IPlugin>

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(IPlugin other)
        {
            return other.Name == Name;
        }

        #endregion

        #region Implementation of IPlugin

        public string Author { get { return "Apoc"; } }

        public Version Version { get { return new Version(0, 1); } }

        public string Name { get { return "Auto-Equip"; } }

        public string Description { get { return "Automatically equips items based on item stat calculations!"; } }

        public Window DisplayWindow { get { return null; } }

        /// <summary> Executes the pulse action. This is called every "tick" of the bot. </summary>
        public void OnPulse()
        {
        }

        /// <summary> Executes the initialize action. This is called at initial bot startup. (When the bot itself is started, not when Start() is called) </summary>
        public void OnInitialize()
        {
            Settings = new AutoEquipSettings();
        }

        /// <summary> Executes the shutdown action. This is called when the bot is shutting down. (Not when Stop() is called) </summary>
        public void OnShutdown()
        {
            Settings.Save();
        }

        /// <summary> Executes the enabled action. This is called when the user has enabled this specific plugin via the GUI. </summary>
        public void OnEnabled()
        {
            GameEvents.InventorySlotsChanged += DoEquip;
        }

        /// <summary> Executes the disabled action. This is called whent he user has disabled this specific plugin via the GUI. </summary>
        public void OnDisabled()
        {
            GameEvents.InventorySlotsChanged -= DoEquip;
        }

        private void Log(string message)
        {
            Logging.WriteDiagnostic("[AutoEquip] " + message);
        }

        #endregion

        public static AutoEquipSettings Settings { get; set; }
        public static AutoEquipSettings.StatWeight CurrentWeights { get; set; }

        public void DoEquip()
        {
            if (BuddyTor.Me.InCombat)
            {
                return;
            }

            if (Settings != null && Settings.Weights != null)
            {
                foreach (AutoEquipSettings.StatWeight w in Settings.Weights)
                {
                    if (w.AdvancedClass == BuddyTor.Me.AdvancedClass && w.CharacterClass == BuddyTor.Me.Class)
                    {
                        CurrentWeights = w;
                        break;
                    }
                }
            }

            if (CurrentWeights == null)
            {
                Log("There are no weights for this class/advanced class!");
                return;
            }

            using (BuddyTor.Memory.AcquireFrame())
            {
                var minQuality = BuddyTor.Me.Level < 6 ? ItemQuality.Standard : ItemQuality.Premium;
                // Cache these so we're not doing multiple lookups. Thanks!
                List<ItemWrapper> equipped = BuddyTor.Me.InventoryEquipped.Where(i => i.Quality >= minQuality).Select(i => new ItemWrapper(i)).ToList();
                List<ItemWrapper> inventory = BuddyTor.Me.InventoryEquipment.Select(i => new ItemWrapper(i)).ToList();

                // Incoming long-hack list of item slot checks...
                EquipItem(SlotId.EquipHumanBelt, equipped, inventory);
                EquipItem(SlotId.EquipHumanChest, equipped, inventory);
                EquipItem(SlotId.EquipHumanFace, equipped, inventory);
                EquipItem(SlotId.EquipHumanFoot, equipped, inventory);
                EquipItem(SlotId.EquipHumanGlove, equipped, inventory);
                EquipItem(SlotId.EquipHumanLeg, equipped, inventory);
                EquipItem(SlotId.EquipHumanWrist, equipped, inventory);

                EquipItem(SlotId.EquipHumanMainHand, equipped, inventory);
                EquipItem(SlotId.EquipHumanOffHand, equipped, inventory);
                EquipItem(SlotId.EquipHumanShield, equipped, inventory);

                EquipItem(SlotId.EquipHumanRanged, equipped, inventory);
                EquipItem(SlotId.EquipHumanRangedPrimary, equipped, inventory);
                EquipItem(SlotId.EquipHumanRangedSecondary, equipped, inventory);
                EquipItem(SlotId.EquipHumanRangedTertiary, equipped, inventory);

                //EquipItem(SlotId.EquipHumanCustomMelee);
                //EquipItem(SlotId.EquipHumanCustomRanged);

                EquipItem(SlotId.EquipHumanEar, equipped, inventory);

                // TODO: Handle these better. You can equip 2 of each, but there's only 1 slot type for them. So we'll need to double-up.
                EquipItem(SlotId.EquipHumanImplant, equipped, inventory);
                EquipItem(SlotId.EquipHumanRelic, equipped, inventory);
            }
        }

        private bool IsRestrictedFromSlot(SlotId slot)
        {
            // Do these checks off the current weights.
            AdvancedClass ac = CurrentWeights.AdvancedClass;
            switch (slot)
            {
                case SlotId.EquipHumanOffHand:
                    // No non-advanced classes can use off-hand weapons!
                    if (ac == 0)
                    {
                        return false;
                    }

                    switch (ac)
                    {
                        case AdvancedClass.Operative:
                        case AdvancedClass.Sniper:
                        case AdvancedClass.Powertech:
                        case AdvancedClass.Juggernaut:
                        case AdvancedClass.Sorcerer:
                        case AdvancedClass.Assassin:
                        case AdvancedClass.Sage:
                        case AdvancedClass.Shadow:
                        case AdvancedClass.Guardian:
                        case AdvancedClass.Scoundrel:
                        case AdvancedClass.Commando:
                        case AdvancedClass.Vanguard:
                            return true;
                    }
                    break;
            }
            return false;
        }

        private void EquipItem(SlotId slot, List<ItemWrapper> equipped, IEnumerable<ItemWrapper> inventory)
        {
            if (IsRestrictedFromSlot(slot))
            {
                return;
            }

            bool isNew;
            ItemWrapper item = GetBestForSlot(slot, equipped, inventory, out isNew);
            if (isNew)
            {
                Logging.Write("[AutoEquip] Equipping {0} into {1}", item.Item.Name, slot);
                BuddyTor.Me.EquipItem(item.Item);
                item.Item.EquipNoConfirm();
                //item.Item.Use();
            }
        }

        #region Comparers

        private ItemWrapper GetBestForSlot(SlotId slot, List<ItemWrapper> equipped, IEnumerable<ItemWrapper> inventory, out bool isNewItem)
        {
            isNewItem = false;
            float bestScore = GetEquippedItem(slot, equipped);

            Log("\tCurrently equiped " + slot + " slot's score is: " + bestScore);

            ItemWrapper best = null;
            if (bestScore != -1f)
            {
                best = equipped.FirstOrDefault(f => f.Item.SlotTypes.Contains(slot));
            }
            foreach (ItemWrapper item in inventory)
            {
                if (!item.CanEquip || !item.Item.SlotTypes.Contains(slot))
                {
                    continue;
                }

                float value = GetItemTotal(item);
                Log("\t\t" + item.Item.Name + " - Value: " + value);
                if (value > bestScore)
                {
                    isNewItem = true;
                    best = item;
                    bestScore = value;
                }
            }
            return best;
        }

        private float GetEquippedItem(SlotId slot, IEnumerable<ItemWrapper> equipped)
        {
            foreach (ItemWrapper item in equipped.Where(item => item.Item.SlotTypes.Contains(slot)))
            {
                return GetItemTotal(item);
            }
            return -1f;
        }

        private float GetItemTotal(ItemWrapper item)
        {
            // Basically; += stat*Modifier to get the value of the item. Huzzah?
            return item.Stats.Sum(itemStat => itemStat.Value * GetModifier(itemStat.Key));
        }

        private float GetModifier(ModStat stat)
        {
            AutoEquipSettings.StatWeight.Modifier mod = CurrentWeights.Modifiers.FirstOrDefault(m => m.Stat == stat);
            return mod != null ? mod.Value : 0f;
        }

        #endregion

        #region Nested type: AutoEquipSettings

        [XmlElement("AutoEquipSettings")]
        public class AutoEquipSettings : XmlSettings
        {
            public AutoEquipSettings() :
                base(Path.Combine(SettingsDirectory, "AutoEquipSettings.xml"))
            {
            }

            [XmlElement("IncludeEnhancementsInStatTotals")]
            [DefaultValue(true)]
            public bool IncludeEnhancementsInStatTotals { get; set; }

            [XmlElement("StatWeights")]
            public List<StatWeight> Weights { get; set; }

            #region Nested type: StatWeight

            [XmlElement("StatWeight")]
            public class StatWeight
            {
                [XmlAttribute("CharacterClass")]
                public CharacterClass CharacterClass { get; set; }

                [XmlAttribute("AdvancedClass")]
                public AdvancedClass AdvancedClass { get; set; }

                [XmlElement("Modifiers")]
                public List<Modifier> Modifiers { get; set; }

                #region Nested type: Modifier

                [XmlElement("Modifier")]
                public class Modifier
                {
                    [XmlAttribute("Stat")]
                    public ModStat Stat { get; set; }

                    [XmlAttribute("Value")]
                    public float Value { get; set; }
                }

                #endregion
            }

            #endregion

            public override void Save()
            {
                // Don't overwrite settings!
            }
        }

        #endregion

        #region Item Wrappers for Optimization

        private class ItemWrapper
        {
            private Dictionary<ModStat, float> _cachedStats;
            private bool? _canEquip;

            public ItemWrapper(TorItem i)
            {
                Item = i;
            }

            public TorItem Item { get; set; }

            public Dictionary<ModStat, float> Stats { get { return _cachedStats ?? (_cachedStats = Item.GetItemStats(Settings.IncludeEnhancementsInStatTotals)); } }

            public bool CanEquip
            {
                get
                {
                    if (_canEquip == null)
                    {
                        _canEquip = BuddyTor.Me.CanEquipItem(Item) == ContainerResult.Ok;
                    }
                    return _canEquip.Value;
                }
            }
        }

        #endregion
    }
}